<template>
  <div ref="canvasContainerRef" :class="$props.class" aria-hidden="true" class="absolute inset-0">
    <canvas ref="canvasRef">
      <div>Your browser does not support canvas</div>
    </canvas>
  </div>
</template>

<script setup lang="ts">
import { useMouse, useDevicePixelRatio } from '@vueuse/core'
import { ref, onMounted, onBeforeUnmount, watch, computed, reactive } from 'vue'

type Circle = {
  x: number
  y: number
  translateX: number
  translateY: number
  size: number
  alpha: number
  targetAlpha: number
  dx: number
  dy: number
  magnetism: number
}

type Props = {
  color?: string
  quantity?: number
  staticity?: number
  ease?: number
  class?: string
}

const props = withDefaults(defineProps<Props>(), {
  color: '#ffffff',
  quantity: 850,
  staticity: 50,
  ease: 50,
  class: '',
})

const canvasRef = ref<HTMLCanvasElement | null>(null)
const canvasContainerRef = ref<HTMLDivElement | null>(null)
const context = ref<CanvasRenderingContext2D | null>(null)
const circles = ref<Circle[]>([])
const mouse = reactive<{ x: number; y: number }>({ x: 0, y: 0 })
const canvasSize = reactive<{ w: number; h: number }>({ w: 0, h: 0 })
const { x: mouseX, y: mouseY } = useMouse()
const { pixelRatio } = useDevicePixelRatio()

// 正确返回 "r,g,b"
const color = computed(() => {
  let hex = props.color.replace(/^#/, '')
  if (hex.length === 3)
    hex = hex
      .split('')
      .map((c) => c + c)
      .join('')
  const bigint = parseInt(hex, 16)
  const r = (bigint >> 16) & 255
  const g = (bigint >> 8) & 255
  const b = bigint & 255
  return `${r},${g},${b}`
})

onMounted(() => {
  if (canvasRef.value) {
    context.value = canvasRef.value.getContext('2d')
  }
  initCanvas()
  animate()
  window.addEventListener('resize', initCanvas)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', initCanvas)
})

watch([mouseX, mouseY], () => {
  onMouseMove()
})

function initCanvas() {
  resizeCanvas()
  drawParticles()
}

function onMouseMove() {
  if (canvasRef.value) {
    const rect = canvasRef.value.getBoundingClientRect()
    const { w, h } = canvasSize
    const x = mouseX.value - rect.left - w / 2
    const y = mouseY.value - rect.top - h / 2
    if (x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2) {
      mouse.x = x
      mouse.y = y
    }
  }
}

function resizeCanvas() {
  if (canvasContainerRef.value && canvasRef.value && context.value) {
    circles.value.length = 0
    canvasSize.w = canvasContainerRef.value.offsetWidth
    canvasSize.h = canvasContainerRef.value.offsetHeight
    canvasRef.value.width = canvasSize.w * pixelRatio.value
    canvasRef.value.height = canvasSize.h * pixelRatio.value
    canvasRef.value.style.width = canvasSize.w + 'px'
    canvasRef.value.style.height = canvasSize.h + 'px'
    context.value.setTransform(1, 0, 0, 1, 0, 0)
    context.value.scale(pixelRatio.value, pixelRatio.value)
  }
}

function circleParams(): Circle {
  return {
    x: Math.random() * canvasSize.w,
    y: Math.random() * canvasSize.h,
    translateX: 0,
    translateY: 0,
    size: Math.random() * 2 + 1,
    alpha: 0,
    targetAlpha: Math.random() * 0.6 + 0.1,
    dx: (Math.random() - 0.5) * 0.2,
    dy: (Math.random() - 0.5) * 0.2,
    magnetism: 0.1 + Math.random() * 4,
  }
}

function drawCircle(circle: Circle, update = false) {
  if (!context.value) return
  const { x, y, translateX, translateY, size, alpha } = circle
  context.value.save()
  context.value.translate(translateX, translateY)
  context.value.beginPath()
  context.value.arc(x, y, size, 0, 2 * Math.PI)
  context.value.fillStyle = `rgba(${color.value}, ${alpha})`
  context.value.fill()
  context.value.restore()
  if (!update) circles.value.push(circle)
}

function clearContext() {
  if (context.value) {
    // 改为透明背景，让父级背景色显示
    context.value.clearRect(0, 0, canvasSize.w, canvasSize.h)
  }
}

function drawParticles() {
  clearContext()
  for (let i = 0; i < props.quantity; i++) {
    drawCircle(circleParams())
  }
}

function remapValue(value: number, start1: number, end1: number, start2: number, end2: number) {
  return ((value - start1) * (end2 - start2)) / (end1 - start1) + start2
}

function animate() {
  clearContext()
  circles.value.forEach((circle, i) => {
    const edge = [
      circle.x + circle.translateX - circle.size,
      canvasSize.w - circle.x - circle.translateX - circle.size,
      circle.y + circle.translateY - circle.size,
      canvasSize.h - circle.y - circle.translateY - circle.size,
    ]
    const closestEdge = Math.min(...edge)
    const remapClosestEdge = remapValue(closestEdge, 0, 20, 0, 1)
    if (remapClosestEdge > 1) {
      circle.alpha += 0.02
      if (circle.alpha > circle.targetAlpha) circle.alpha = circle.targetAlpha
    } else {
      circle.alpha = circle.targetAlpha * remapClosestEdge
    }
    circle.x += circle.dx
    circle.y += circle.dy
    circle.translateX +=
      (mouse.x / (props.staticity / circle.magnetism) - circle.translateX) / props.ease
    circle.translateY +=
      (mouse.y / (props.staticity / circle.magnetism) - circle.translateY) / props.ease
    if (
      circle.x < -circle.size ||
      circle.x > canvasSize.w + circle.size ||
      circle.y < -circle.size ||
      circle.y > canvasSize.h + circle.size
    ) {
      circles.value.splice(i, 1)
      drawCircle(circleParams())
    } else {
      drawCircle(circle, true)
    }
  })
  requestAnimationFrame(animate)
}
</script>

<style scoped>
.absolute {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 0; /* 确保在底部 */
  pointer-events: none; /* 重要：防止拦截鼠标事件 */
}

canvas {
  display: block;
  pointer-events: none; /* 画布也不拦截事件 */
}
</style>
